Skip to content

fix: guard formatNumber against non-numeric values - #1721

Merged
kodiakhq[bot] merged 9 commits into
ClickHouse:mainfrom
themavik:fix/number-format-nan-1510
Feb 26, 2026
Merged

kodiakhq[bot] merged 9 commits into
ClickHouse:mainfrom
themavik:fix/number-format-nan-1510

Conversation

@themavik

Copy link
Copy Markdown
Contributor

Summary

Fixes #1510

Root cause: When a number format (e.g., Percent) is applied to table columns that contain non-numeric values, formatNumber doesn't catch non-empty strings. numbro() then produces NaN.

The existing check !value && value !== 0 catches null, undefined, and NaN, but NOT non-empty strings (which are truthy and pass through to numbro()).

Changes

  • packages/app/src/utils.ts: Added typeof value !== 'number' || isNaN(value) guard that returns the raw value as a string instead of formatting it.

Risk Assessment

Low - Only adds a guard for an edge case. Numeric values are formatted exactly as before.

When table columns contain non-numeric values (strings, etc.)
and a number format like Percent is applied, the formatting
produces NaN because `numbro()` can't parse strings. The
existing falsy check (`!value && value !== 0`) doesn't catch
non-empty strings since they're truthy.

Adds a `typeof` and `isNaN` guard to return the raw value
as-is when it's not actually numeric.

Fixes #1510
@changeset-bot

changeset-bot Bot commented Feb 10, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 5a572ca

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/app Patch
@hyperdx/api Patch
@hyperdx/otel-collector Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Feb 10, 2026

Copy link
Copy Markdown

@themavik is attempting to deploy a commit to the HyperDX Team on Vercel.

A member of the Team first needs to authorize it.

@vercel

vercel Bot commented Feb 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-v2-oss-app Ready Ready Preview, Comment Feb 17, 2026 8:08am

Request Review

@karl-power
karl-power self-requested a review February 18, 2026 14:58
@MikeShi42

Copy link
Copy Markdown
Contributor

@themavik I think we should only guard for isNaN - as it is possible for ClickHouse to return number values encoded as string due to https://clickhouse.com/docs/operations/settings/formats#output_format_json_quote_64bit_integers

@karl-power karl-power left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this change doesn't address the root cause of the attached issue.

The problem is that grouping columns can exist which aren't found in the groupByKeys array here. The number formatter is then applied to these columns and we see NaN.

That can happen by adding a group by like ServiceName as SN or ResourceAttributes['host.id'] and the resulting column name (SN and arrayElement(ResourceAttributes, 'host.id') respectively) is not found in the groupByKeys array.

A solution would be to extend the code that creates the groupByKeys to check if aliases are used, and add the alias only to the array.

- Remove typeof check; ClickHouse returns numbers as strings
- Only guard for isNaN to catch truly non-numeric values

Co-authored-by: Cursor <cursoragent@cursor.com>
@themavik

Copy link
Copy Markdown
Contributor Author

Thanks for the feedback. I've narrowed the guard to only check isNaN as @MikeShi42 suggested, since ClickHouse can return numbers as strings.

@karl-power — good point about groupByKeys not handling aliases. The alias handling could be a good follow-up improvement; this PR focuses on the immediate formatting crash.

@github-actions

github-actions Bot commented Feb 20, 2026 •

Copy link
Copy Markdown
Contributor

Code Review

  • ⚠️ Root cause not fixed → groupByKeys in DBTableChart.tsx:115 splits the raw groupBy string but doesn't extract aliases (e.g. ServiceName as SN → key is SN, but groupByKeys contains ServiceName as SN), so groupByKeys.includes(key) returns false for aliased columns and numberFormat is incorrectly applied. This PR is a defensive patch, but the alias handling should also be fixed to prevent the misconfiguration in the first place.

  • ⚠️ No test coverage for new behavior → Add test cases for formatNumber(NaN), formatNumber('hello' as any), and formatNumber('123' as any) to document and protect the new guard.

  • ⚠️ Type signature vs actual usage → The function accepts value?: number but is called with string values from ClickHouse. The as number cast suppresses the TS error without enforcing correctness. Consider broadening the signature to value?: number | string or fixing the call sites so the type system catches future misuse.

The fix logic itself (isNaN coercion handles both NaN and non-numeric strings) is sound, but the above should be addressed before merge.

@karl-power

Copy link
Copy Markdown
Contributor

@themavik Can you cover the isNaN check with a test case in packages/app/src/__tests__/utils.test.ts?

karl-power
karl-power previously approved these changes Feb 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Code Review

  • ⚠️ Misleading comment → The comment says "Guard against NaN only" but isNaN('hello') is true due to JS coercion, so this also guards against non-numeric strings. Update to: "Guard against non-numeric string values (isNaN coerces strings, catching both NaN and non-parseable strings like 'hello', while numeric strings like '123' pass through to numbro)"

  • ⚠️ Type signature mismatch → value?: number doesn't reflect actual call sites where ClickHouse returns strings; tests need @ts-expect-error to suppress it. Consider broadening to value?: number | string so the type system documents the real contract and catches future misuse without casts.

  • 📝 NaN test cases cover pre-existing behavior → formatNumber(NaN) already returns 'N/A' via the existing \!value && value \!== 0 check (NaN is falsy). The new guard only affects non-numeric strings. The NaN tests are fine for documentation, but the real new coverage is the string test.

✅ The fix logic itself is correct and aligns with @MikeShi42's feedback — numeric strings from ClickHouse ('123') pass through to numbro while non-numeric strings ('hello') are returned as-is.

@github-actions

Copy link
Copy Markdown
Contributor

Code Review

  • ⚠️ Dead code path for NaN → isNaN(value as number) is unreachable for actual NaN values — the existing \!value && value \!== 0 check above already returns 'N/A' for NaN (since \!NaN === true). The new guard only ever fires for non-numeric strings like 'hello'. This means the NaN unit tests are testing pre-existing behavior, not the new code. The comment saying "Guard against NaN only" is doubly misleading.

  • ⚠️ Misleading comment → Comment says "Guard against NaN only" but the actual new behavior is guarding non-numeric strings via JS coercion (isNaN('hello') → true). Update comment to accurately describe what's happening.

  • ⚠️ Type signature mismatch → value?: number doesn't reflect actual call sites (ClickHouse returns numeric strings); tests require @ts-expect-error to silence TS. Consider widening to value?: number | string so the type contract is honest and the tests don't need suppressions.

  • ℹ️ Root cause unaddressed (noted by @karl-power, acknowledged by author) → groupByKeys in DBTableChart.tsx doesn't handle aliases like ServiceName as SN, causing numberFormat to be misapplied to non-numeric columns in the first place. This PR is a reasonable defensive patch, but a follow-up to fix alias extraction would prevent the misconfiguration entirely.

✅ The core fix logic is correct — numeric strings from ClickHouse ('123') pass isNaN and reach numbro, while non-numeric strings ('hello') return as-is. Low risk, appropriate as a patch release.

@kodiakhq
kodiakhq Bot merged commit 3797e65 into ClickHouse:main Feb 26, 2026
13 of 14 checks passed
knudtty pushed a commit that referenced this pull request Apr 16, 2026
## Summary

Fixes #1510

**Root cause:** When a number format (e.g., Percent) is applied to table columns that contain non-numeric values, `formatNumber` doesn't catch non-empty strings. `numbro()` then produces NaN.

The existing check `!value && value !== 0` catches `null`, `undefined`, and `NaN`, but NOT non-empty strings (which are truthy and pass through to `numbro()`).

## Changes

- `packages/app/src/utils.ts`: Added `typeof value !== 'number' || isNaN(value)` guard that returns the raw value as a string instead of formatting it.

## Risk Assessment

**Low** - Only adds a guard for an edge case. Numeric values are formatted exactly as before.

Co-authored-by: Karl Power <85935352+karl-power@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Number format for tables should not be applied for non-numeric values (grouping keys)

3 participants